//!
//! @brief This program tests the Manifold :: isSafeToProcess() function,
//! which uses LBTS to find the lower bound time stamp in the whole system
//! and then determine whether an event can be safely processed.
//! In this program we don't actually schedule any events. Instead, we create
//! a series of random numbers to simulate event timestamps. The processes
//! don't send messages to each other either, except, of course, the allGather
//! used in LBTS.
//!
//! This program can be run with N (N>1) LPs. Each creates an arrary of increasing
//! values representing timestamps. Then it goes through the array and checks
//! if the timestamps are safe to process. At each step, the granted time of LBTS
//! is saved in a vector. Finally, we collect all the timestamps and verify the
//! granted time vector has exactly the same values.
//!
#include <TestFixture.h>
#include <TestAssert.h>
#include <TestSuite.h>
#include <Test.h>
#include <TestCaller.h>
#include <cppunit/ui/text/TestRunner.h>

#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <stdio.h>
#include "mpi.h"
#include "manifold.h"
#include "component.h"
#include "messenger.h"
#include "scheduler.h"

using namespace std;
using namespace manifold::kernel;


//####################################################################
// for qsort
//####################################################################
int compdouble(const void* a1, const void* a2)
{
    const double d1 = *(const double*)a1;
    const double d2 = *(const double*)a2;
    if(d1 < d2)
        return -1;
    else if(d1 > d2)
        return 1;
    else
        return 0;
}

//####################################################################
// ManifoldTest is the unit test class for the class Manifold.
//####################################################################
class ManifoldTest : public CppUnit::TestFixture {

    public:
        /**
	 * Initialization function. Inherited from the CPPUnit framework.
	 */
        void setUp()
	{
	}

        //! Test the Manifold :: isSafeToProcess() function.
	//! Each MPI task creates a series of random numbers in increasing 
	//! order to represent timestamps of events.
	//! The first number is randomly generated; the rest are generated by
	//! adding a random interval to the previous number.
	//! Then we go through the series and simulating event processing.
        void testIsSafeToProcess()
	{
	    int Mytid; //task id
	    MPI_Comm_rank(MPI_COMM_WORLD, &Mytid);

	    char buf[10];
	    sprintf(buf, "DBG_LOG%d", Mytid);
	    ofstream DBG_LOG(buf);

	    const int SIZE=20;  // SIZE numbers are generated
	    double when[SIZE+1];

	    srandom((Mytid+123)*123);
	    when[0] = random()/(RAND_MAX+1.0) * 10;  //a number between 0 and 10
	    for(int i=1; i<SIZE; i++) {
	        //when[i] = when[i-1] + d;   1 <= d < 6
	        when[i] = when[i-1] + (random()/(RAND_MAX+1.0) * 5 + 1);
	    }
	    when[SIZE] = 1000000;
	    CPPUNIT_ASSERT(when[SIZE-1] < when[SIZE]);

            for(int i=0; i<SIZE+1; i++) {
	        DBG_LOG << when[i] << " ";
	    }
	    DBG_LOG << endl;

            vector<double> grantedHistory;

            Scheduler* sch = Manifold :: get_scheduler();

            //go through the numbers.
	    for(int i=0; i<SIZE+1; i++) {
		DBG_LOG << "### processing event " << i+1 << " @" << when[i] << endl;
	        int count = 0;
	        while(true) {
		    double old_granted = sch->get_grantedTime();
		    DBG_LOG << ++count << " calls..." << endl;
		    if(sch->call_isSafeToProcess(when[i])) {
		        CPPUNIT_ASSERT(when[i] <= sch->get_grantedTime());
			grantedHistory.push_back(sch->get_grantedTime());

		        DBG_LOG << "safe to process: req= " << when[i] << ", granted= " << sch->get_grantedTime() << endl;
			DBG_LOG << "old granted= " << old_granted << ", new granted= " << sch->get_grantedTime() << endl;
			break;
		    }
		    else {
		        CPPUNIT_ASSERT(when[i] > sch->get_grantedTime());
			grantedHistory.push_back(sch->get_grantedTime());

		        DBG_LOG << "not safe." << endl;
			DBG_LOG << "old granted= " << old_granted << ", new granted= " << sch->get_grantedTime() << endl;
		    }
		}//while
	    }//for

	    //##### now do some verification
	    int node_size =  TheMessenger.get_node_size();

	    double* all_ts = new double[SIZE * node_size];

            //gather timestamps from all LPs
	    MPI_Allgather(when, SIZE, MPI_DOUBLE, all_ts, SIZE, MPI_DOUBLE, MPI_COMM_WORLD);

            //sort the timestamps
	    qsort(all_ts, SIZE*node_size, sizeof(double), compdouble);

	    for(int i=0; i<SIZE*node_size; i++)
	        DBG_LOG << all_ts[i] << " ";
            DBG_LOG << endl;

            CPPUNIT_ASSERT_EQUAL(SIZE*node_size, (int)grantedHistory.size()-1); //don't count the final timestamp

	    for(int i=0; i<SIZE*node_size; i++) {
	        CPPUNIT_ASSERT_EQUAL(all_ts[i], grantedHistory[i]);
	    }
	}


        /**
	 * Build a test suite.
	 */
	static CppUnit::Test* suite()
	{
	    CppUnit::TestSuite* mySuite = new CppUnit::TestSuite("ManifoldTest");

	    mySuite->addTest(new CppUnit::TestCaller<ManifoldTest>("testIsSafeToProcess", &ManifoldTest::testIsSafeToProcess));

	    return mySuite;
	}
};



int main(int argc, char** argv)
{
    Manifold :: Init(argc, argv);
    if(TheMessenger.get_node_size() < 2) {
        cerr << "ERROR: Must specify \"-np N (N > 1)\" for mpirun!" << endl;
	return 1;
    }

    CppUnit::TextUi::TestRunner runner;
    runner.addTest( ManifoldTest::suite() );
    runner.run();

    Manifold :: Finalize();

    return 0;
}

